package com.bree.erp.jsf.view;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.security.DeclareRoles;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.ViewScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.birt.report.engine.api.EngineException;

import com.bree.erp.entity.PoAdjustment;
import com.bree.erp.entity.PoItem;
import com.bree.erp.entity.PurchaseOrder;
import com.bree.erp.entity.Status;
import com.bree.erp.entity.Warehouse;
import com.bree.erp.jsf.httpsess.SessionDataKey;
import com.bree.erp.jsf.httpsess.UserSessionData;
import com.bree.erp.jsf.util.CurrentInstance;
import com.bree.erp.report.birt.PurchaseOrderForm;
import com.bree.erp.report.birt.param.PurchaseOrderFormParam;
import com.bree.erp.report.entity.po.Item;
import com.bree.erp.report.entity.po.PurchaseOrderDataSet;
import com.bree.erp.service.PoItemService;
import com.bree.erp.service.PurchaseOrderService;
import com.bree.erp.service.PurchaseRequisitionService;
import com.bree.erp.service.WarehouseService;

@DeclareRoles("PURCHASER")
@ManagedBean(name = "dtPurchaseOrderEditView")
@ViewScoped
public class PurchaseOrderEditView implements Serializable {

	private static final long serialVersionUID = 1L;

	private static Logger LOG = Logger.getLogger(PurchaseOrderEditView.class);

	@ManagedProperty(value = "#{userSession}")
	private UserSessionData userSession;

	@ManagedProperty(value = "#{dtPurchaseOrderAdjustmetView}")
	private PurchaseOrderAdjustmentView adjustmentView;

	@ManagedProperty(value = "#{dtPurchaseOrderItemsView}")
	private PurchaseOrderItemsView orderItemsView;

	@EJB
	private PurchaseOrderService poService;

	@EJB
	private PoItemService poItemService;

	@EJB
	private WarehouseService warehouseService;

	@EJB
	private PurchaseRequisitionService prService;

	private PurchaseOrder purchaseOrder;
	private List<Warehouse> warehouses;

	private boolean dirty;

	private String warehouse;
	private String term;
	private Date dueDate;
	private String remarks;

	@PostConstruct
	public void init() {

		Long id = userSession.get(SessionDataKey.SELECTED_PURCHASE_ORDER_ID,
				Long.class);
		LOG.debug("SELECTED_PURCHASE_ORDER_ID: " + id);
		if (id != null) {
			purchaseOrder = poService.read(id, true);

			if (purchaseOrder.getWarehouse() != null) {
				warehouse = purchaseOrder.getWarehouse().getId();
			}

			this.term = purchaseOrder.getTerms();
			this.dueDate = purchaseOrder.getDueDate();
			this.remarks = purchaseOrder.getRemarks();

			adjustmentView.load(purchaseOrder);
			orderItemsView.loadAsCollector(purchaseOrder);
		}

		warehouses = warehouseService.list();

	}

	public String loadMain() {

		Long prId = purchaseOrder.getPurchaseRequisition().getId();
		userSession.put(SessionDataKey.SELECTED_PURCHASE_REQUISITION_ID, prId);

		return "/po/main";
	}

	public double getTotal() {

		double total = adjustmentView.total() + orderItemsView.total();

		return total;

	}

	private Long savePoIdParamInSession() {

		Long id = CurrentInstance.getLongParameter("poId");

		LOG.debug("Persist in session PurchaseOrder#: " + id);
		userSession.put(SessionDataKey.SELECTED_PURCHASE_ORDER_ID, id);

		return id;

	}

	public String openPurchaseOrder() {

		savePoIdParamInSession();

		init();

		return "/po/edit";
	}

	public String loadPoFromDialog() {

		Long id = savePoIdParamInSession();

		PurchaseOrder po = poService.read(id, true);

		if (po != null) {

			switch (po.getStatus()) {
			case CONFIRMED:
			case PARTIAL:
				return "/inv/receive";
			case RECEIVED:
				return "/inv/withdraw";
			case WITHDRAW:
				return "/inv/pickup";
			case CLOSED:
				return "/inv/archive";
			default:
				LOG.error("PO#[" + id + "][status=" + po.getStatus()
						+ "] is not supported here!");
				break;
			}
		} else {
			LOG.error("PO# " + id + " is NULL!");
		}

		return null;
	}

	public void deletePoItem(Long id) {

		LOG.debug("deleting PoItem#: " + id);

		poItemService.delete(id);
	}

	private PurchaseOrder update(Status status) {

		PurchaseOrder po = poService.update(this.purchaseOrder.getId(),
				this.warehouse, this.term, this.dueDate, orderItemsView
						.getFordeletion(), adjustmentView.getFordeletion(),
				adjustmentView.getAdjustments(), status, remarks, 
					userSession.getLoggedInUser().getEmail());

		this.setDirty(false);
		adjustmentView.setDirty(false);
		orderItemsView.setDirty(false);

		return po;
	}

	public String save() {
		LOG.debug("saving... :" + this.term);

		update(null);

		return "/po/edit";
	}

	public String saveAndSubmit() {
		LOG.debug("saving and submit ... :" + this.term);

		update(Status.PENDING);

		prService.updateStatusFromOrders(purchaseOrder.getPurchaseRequisition()
				.getId());

		return "/po/edit";
	}

	private void updateStatus(Status status) {

		poService.update(this.purchaseOrder.getId(), status, userSession
				.getLoggedInUser().getEmail());
	}

	public String reopen() {

		updateStatus(Status.OPEN);

		return "/po/edit";
	}

	public String approve() {

		updateStatus(Status.APPROVED);
		
		// complete sequence number
		poService.updateSequence(this.purchaseOrder.getId());

		return "/po/edit";
	}

	public String acknowledge() {

		updateStatus(Status.CONFIRMED);

		return "/po/edit";

	}

	public String receive() {

		updateStatus(Status.RECEIVED);

		return "/po/edit";

	}

	public String withdraw() {

		updateStatus(Status.WITHDRAW);

		return "/po/edit";

	}

	public String complete() {

		updateStatus(Status.CLOSED);

		return "/po/edit";

	}

	public void printPdf() throws IOException, EngineException {

//		HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext
//				.getCurrentInstance().getExternalContext().getResponse();
//		httpServletResponse.addHeader("Content-disposition",
//				"attachment; filename=report.pdf");
//		ServletOutputStream servletOutputStream = httpServletResponse
//				.getOutputStream();
//		
//		new PurchaseOrderForm().render(servletOutputStream);
//		
//		FacesContext.getCurrentInstance().responseComplete();

		FacesContext fc = FacesContext.getCurrentInstance(); 
	    ExternalContext ec = fc.getExternalContext();
	    
	    String filename = purchaseOrder.getPoNumber() == null ? ("PO-" + purchaseOrder.getId()) : purchaseOrder.getPoNumber().toString();
	    filename += ".pdf";

	    ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
	    ec.setResponseContentType("application/pdf"); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
	    //ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
	    ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

	    OutputStream output = ec.getResponseOutputStream();
	    
	    
	    PurchaseOrderFormParam param = new PurchaseOrderFormParam();
	    
	    String supplierAddress = this.purchaseOrder.getSupplier().getName() + "\n" +
	    		this.purchaseOrder.getSupplier().getAddress().toString() + "\n" + 
	    		"ATTN: " + this.purchaseOrder.getSupplier().getAddress().getContactPerson();
	    param.setSupplierAddress(supplierAddress);
	    
	    String warehouseAddress = this.purchaseOrder.getWarehouse().getName() + "\n" +
	    		this.purchaseOrder.getWarehouse().getAddress().toString() + "\n" +
	    		"ATTN: " + this.purchaseOrder.getWarehouse().getAddress().getContactPerson();
	    param.setWarehouseAddress(warehouseAddress);
	    param.setDueDate(CurrentInstance.printDate(purchaseOrder.getDueDate()));
	    
	    param.setPoNumber(purchaseOrder.getPoNumber() == null ? "N/A" : purchaseOrder.getPoNumber().toString());
	    param.setPurchaseDate(CurrentInstance.printDate(purchaseOrder.getOrderDate()));
	    param.setPurchaser(purchaseOrder.getCreatedBy().getDisplayName());
	    param.setRemarks(purchaseOrder.getRemarks());
	    param.setTerms(purchaseOrder.getTerms());
	    param.setTotalAmount(purchaseOrder.getTotal());
	    param.setApprover(purchaseOrder.getApprovedBy() == null ? "" : purchaseOrder.getApprovedBy().getDisplayName());
	    param.setPrNumber(purchaseOrder.getPurchaseRequisition().getPrNumber().toString());
	    
	    // Now you can write the InputStream of the file to the above OutputStream the usual way.
	    PurchaseOrderForm purchaseOrderForm = new PurchaseOrderForm(param);
	    PurchaseOrderDataSet purchaseOrderDataSet = transformToBirtDataSet(this.purchaseOrder);
		purchaseOrderForm.setPurchaseOrderDataSet(purchaseOrderDataSet );
	    
	    purchaseOrderForm.render(output);

	    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.

		
	}

	private PurchaseOrderDataSet transformToBirtDataSet(
			PurchaseOrder po) {

		List<Item> items = new ArrayList<Item>();
		
		int count = 0;
		for(PoItem poItem : po.getItems()) {
			
			Item item = new Item();
			
			item.setItemNumber(++count);
			
			Double quantity = poItem.getQuantity();
			String unit = poItem.getPartUnit().getShortUnit();
			Double pricePerUnit = poItem.getPricePerUnit();
			Double totalPrice = poItem.getTotalPrice();
			String brand = poItem.getPartUnit().getShortBrand();
			String partNum = poItem.getPartUnit().getPart();
			
			StringBuilder sb = new StringBuilder(poItem.getPartUnit().getShortName());
			if(StringUtils.isNotBlank(brand)) {
				sb.append(" ").append(brand);
			}
			if(StringUtils.isNotBlank(partNum)) {
				sb.append(" ").append(partNum);
			}
			
			item.setDescription(sb.toString());
			sb = null;
			item.setPricePerUnit(pricePerUnit);
			item.setQuantity(quantity);
			item.setUnit(unit);
			item.setTotalPrice(totalPrice);
			
			items.add(item);
		}
		
		for(PoAdjustment adjustment : po.getAdjustments()) {

			Item item = new Item();
			
			item.setItemNumber(++count);
			item.setDescription(adjustment.getDescription());
			item.setTotalPrice(adjustment.getAmount());
			
		}
		
		return new PurchaseOrderDataSet(items);
	}

	public void markDirty() {
		this.setDirty(true);
	}

	public boolean isShowSubmit() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.OPEN;
	}

	public boolean isEditable() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.OPEN;
	}

	public boolean isShowReopen() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.PENDING;
	}

	public boolean isShowApprove() {
		return this.purchaseOrder != null
				&& this.userSession.isManager()
				&& this.purchaseOrder.getStatus() == Status.PENDING;
	}

	public boolean isShowAcknowledge() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.APPROVED;
	}

	public boolean isShowReceived() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.CONFIRMED;
	}

	public boolean isShowWithdraw() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.RECEIVED;
	}

	public boolean isShowClose() {
		return this.purchaseOrder != null
				&& this.purchaseOrder.getStatus() == Status.WITHDRAW;
	}

	/**
	 * @return the dirty
	 */
	public boolean isDirty() {
		return dirty || adjustmentView.isDirty() || orderItemsView.isDirty();

	}

	public List<Warehouse> getWarehouses() {
		return warehouses;
	}

	/**
	 * @return the userSession
	 */
	public UserSessionData getUserSession() {
		return userSession;
	}

	/**
	 * @param userSession
	 *            the userSession to set
	 */
	public void setUserSession(UserSessionData userSession) {
		this.userSession = userSession;
	}

	/**
	 * @return the purchaseOrder
	 */
	public PurchaseOrder getPurchaseOrder() {
		return purchaseOrder;
	}

	/**
	 * @param purchaseOrder
	 *            the purchaseOrder to set
	 */
	public void setPurchaseOrder(PurchaseOrder purchaseOrder) {
		this.purchaseOrder = purchaseOrder;
	}

	/**
	 * @return the warehouse
	 */
	public String getWarehouse() {
		return warehouse;
	}

	/**
	 * @param warehouse
	 *            the warehouse to set
	 */
	public void setWarehouse(String warehouse) {
		this.warehouse = warehouse;
	}

	/**
	 * @return the term
	 */
	public String getTerm() {
		return term;
	}

	/**
	 * @param term
	 *            the term to set
	 */
	public void setTerm(String term) {
		this.term = term;
	}

	/**
	 * @return the adjustmentView
	 */
	public PurchaseOrderAdjustmentView getAdjustmentView() {
		return adjustmentView;
	}

	/**
	 * @param adjustmentView
	 *            the adjustmentView to set
	 */
	public void setAdjustmentView(PurchaseOrderAdjustmentView adjustmentView) {
		this.adjustmentView = adjustmentView;
	}

	/**
	 * @return the orderItemsView
	 */
	public PurchaseOrderItemsView getOrderItemsView() {
		return orderItemsView;
	}

	/**
	 * @param orderItemsView
	 *            the orderItemsView to set
	 */
	public void setOrderItemsView(PurchaseOrderItemsView orderItemsView) {
		this.orderItemsView = orderItemsView;
	}

	/**
	 * @param dirty
	 *            the dirty to set
	 */
	public void setDirty(boolean dirty) {
		this.dirty = dirty;
	}

	/**
	 * @return the dueDate
	 */
	public Date getDueDate() {
		return dueDate;
	}

	/**
	 * @param dueDate
	 *            the dueDate to set
	 */
	public void setDueDate(Date dueDate) {
		this.dueDate = dueDate;
	}

	/**
	 * @return the remarks
	 */
	public String getRemarks() {
		return remarks;
	}

	/**
	 * @param remarks the remarks to set
	 */
	public void setRemarks(String remarks) {
		this.remarks = remarks;
	}

}
